/* -----------------------------------------------------------------------------
 * PROJECT    : Random
 * FILE       : RandomNumberGenerator.java
 *
 * HISTORY:
 * Date          Author                        Description
 * ----------------------------------------------------------------------------- 
 * Jul 24, 2008  Raghu Sarraff			       Created
 */

package net.lwf.number.util;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import net.lwf.common.util.SortingUtil;


/**
 * Type the class description here.
 * 
 * @author Raghu Sarraff
 * 
 * @version $Id$
 */
public class RandomNumberGenerator {

    final static int[] sizeTable = { 0, 9, 99, 999, 9999, 99999, 999999,
            9999999, 99999999, 999999999, Integer.MAX_VALUE };

    private static Random random = new Random();

    /**
     * Generates the random number for the given number of digits.
     * Ex. if the number of digits = 3, the generated number could be 459 or so
     * If the given number of digits are negative, the random number will be negative.
     * 
     * @param digit Number of required degits
     * 
     * @return
     */
    public static int generate(int digit) {
    	//Random random = new Random();
        boolean negativenumber = false;
        if(digit < 0)  {
            digit = -(digit);
            negativenumber = true;
        }
        if(digit == 0) return 0;
        if(digit > Integer.MAX_VALUE) digit = Integer.MAX_VALUE; //May not be required
        int highest = sizeTable[digit] + 1;
        int lowest = sizeTable[digit - 1] + 1;

        int generated = random.nextInt(highest);

        if (generated < lowest)
            generated = generate(digit);

        if(negativenumber) {
            return -(generated);
        }
        return generated;
    }      
    
    /**
     * Generates a random number with decimal points.
     * 
     * @param leftdigits   Required digits before the decimal point
     * @param rightdigits  Required digits after the decimal point 
     */
    public  static double generateDouble(int leftdigits, int rightdigits) {
       int number = generate(leftdigits);
       //Random random = new Random();
       double randomdouble = number + random.nextDouble();
       DecimalFormat df= new DecimalFormat(generatePattern(leftdigits, rightdigits)); 
       String formatted = df.format(randomdouble);
       return Double.parseDouble(formatted);
       //return df.format(randomdouble);
    } //generateDouble
    
    
    
	  private static double getGaussian(double aMean, double aVariance){
	      return aMean + random.nextGaussian() * aVariance;
	  }
	  
  public static List<Double> generateDouble(double base, int variance, int required) {
  	List<Double> doubles = new ArrayList<Double>();
  	for(int i=0; i<required; i++) {
  		double generated = getGaussian(base, variance);
  		DecimalFormat df= new DecimalFormat(generatePattern(base)); 
  		String formattedvalue = df.format(generated);
  		doubles.add(Double.parseDouble(formattedvalue));
  	}
  	return doubles;
  }
    /**
     * Generates a float random number.
     * 
     * @param leftdigits   Required digits before the decimal point
     * @param rightdigits  Required digits after the decimal point 
     */    
    public void generateFloat(int digit) {
        int number = generate(digit);
        //Random random = new Random();
        float randomfloat = number + random.nextFloat();
        //DecimalFormat df= new DecimalFormat("0.##"); 
    } 
    
    public static int generate(int aStart, int aEnd){
    	//Random random = new Random();
        if ( aStart > aEnd ) {
          throw new IllegalArgumentException("Start cannot exceed End.");
        }
        //get the range, casting to long to avoid overflow problems
        long range = (long)aEnd - (long)aStart + 1;
        // compute a fraction of the range, 0 <= frac < range
        long fraction = (long)(range * random.nextDouble());
        return  (int)(fraction + aStart);    
      }
    
    private static String generatePattern(double value) {
  	  String sValue = String.valueOf(value);
  	  int decimal = sValue.indexOf(".");
  	  int leftdigits = 0;
  	  int rightdigits = 0;
  	  if(decimal != -1) {
      	  leftdigits = (sValue.substring(0, (decimal - 1))).length();
      	  rightdigits = (sValue.substring(decimal+1)).length();
  	  }
  	  else {
  		  leftdigits = sValue.length();
  	  }

  	  return generatePattern(leftdigits, rightdigits);
    }
    
//    value pattern output 
//    1 2 3 
//    123456.789 ###,###.### 123, 456.789 
//    The pound sign (#) denotes a digit, the comma is a placeholder for the grouping separator, and the period is a placeholder for the decimal separator. 
//    123456.789 ###.## 123456.79 
//    The value has three digits to the right of the decimal point, but the pattern has only two. The format method handles this by rounding up. 
//    123.78 000000.000 000123.780 
//    The pattern specifies leading and trailing zeros, because the 0 character is used instead of the pound sign (#). 
//    12345.67 $###,###.### $12, 345.67 
//    The first character in the pattern is the dollar sign ($). Note that it immediately precedes the leftmost digit in the formatted output. 
//    12345.67 \u00A5###,###.### �12,345.67 
    
    //decimalpoints digits after decimal point
    private  static  String generatePattern(int leftdigits, int rightdigits) {
        String leftString = "";
        for(int i=0; i<leftdigits; i++) {
            leftString += "#";
        }
        String rightString = "";
        for(int i=0; i<rightdigits; i++) {
            rightString += "#";
        }
        return leftString + "." + rightString;
    } //generatePattern

	public static String[] generate(int start, int end, int required) {
		String[] sarray = new String[required];
		String numb = String.valueOf(RandomNumberGenerator.generate(start, end));
		Map mapData = new HashMap(required);
		for(int i=0; i<required; i++) {
			while(mapData.containsKey(numb)) {
				numb = String.valueOf(RandomNumberGenerator.generate(start, end));
			}
			mapData.put(numb, numb);
			sarray[i] = String.valueOf(numb);
		}
		
		return sarray;
	}
	
	/**
	 * Generates required unique random numbers.
	 * 
	 * @param digits   Digits required in the number
	 * @param required Required random numbers
	 * 
	 * @return List of Integers
	 */
	public static List<Integer> generateInts(int digits, int required) {
		List<Integer> lstNumbers = new ArrayList<Integer>(required);
		Map<Integer, Integer> mapData = new HashMap<Integer, Integer>(required);
		for(int i=0; i<required; i++) {
			Integer integer = new Integer(RandomNumberGenerator.generate(digits));
			while(mapData.containsKey(integer)) {
				integer = new Integer(RandomNumberGenerator.generate(digits));
			}
			mapData.put(integer, integer);
			lstNumbers.add(integer);
		}
		
		return lstNumbers;
	}
	
	/**
	 * Generates required unique random numbers.
	 * 
	 * @param base     base number
	 * @param variance
	 * @param required Required random numbers
	 * 
	 * @return List of Integers
	 */
	public static List<Integer> generateIntsWithVariance(int base, int variance, int required) {
		int digits = String.valueOf(base).length();
		List<Integer> lstNumbers = new ArrayList<Integer>(required);
		Map<Integer, Integer> mapData = new HashMap<Integer, Integer>(required);
		for(int i=0; i<required; i++) {
			boolean generate = false;
			while(!generate) {
				int value = RandomNumberGenerator.generate(digits);
				int diff = base - value;
				if(diff < 0) {
					diff = -1 * diff;
				}
				if(diff <= variance) {
					Integer integer = new Integer(value);
					if(!mapData.containsKey(integer)) {
						mapData.put(integer, integer);
						lstNumbers.add(integer);
						generate = true;
					}
				}
			}

		}
		
		return lstNumbers;
	}
	
	/**
	 * @TODO refactoring needed to remove break command......
	 * Actually generating more than required numbers.....
	 * 
	 * Generates required unique random numbers.
	 * 
	 * @param base     base number
	 * @param variance
	 * @param required Required random numbers
	 * 
	 * @return List of Integers
	 */
	public static List<Integer> generateIntsWithVariance(int base, int maxvalue, int variance, int required) {
		int digits = String.valueOf(base).length();
		List<Integer> lstNumbers = new ArrayList<Integer>(required);
		Map<Integer, Integer> mapData = new HashMap<Integer, Integer>(required);
		for(int i=0; i<required; i++) {
			boolean generate = false;
			while(!generate) {
				Integer integer = 0;
				boolean generated = false;
				while(!generated) {
					int value = RandomNumberGenerator.generate(digits);
					int diff = base - value;
					if(diff < 0) {
						diff = -1 * diff;
					}
					if(diff <= variance) {
						integer = new Integer(value);
						if(!mapData.containsKey(integer)) {
							mapData.put(integer, integer);
							if(lstNumbers.size() == required) {
								break;
							}
							lstNumbers.add(integer);
							generate = true;
						}
					}
					if(value <= maxvalue) {
						generated = true;
					}
				}
			}

		}
		
		return lstNumbers;
	}	
	
	//Generates the unique number in the given range. No repetitions :)
	public static int generateInt(int start, int end) {
		boolean uniquenumber = false;
		int number = 0;
		int nCounter = 0;
		while(!uniquenumber) {
			number = RandomNumberGenerator.generate(start, end);
			Map mapdata = new HashMap();
			String sNumber = String.valueOf(number);
			int nLen = sNumber.length();			
			for(int i=0; i<nLen; i++) {
				String value = null;
				if(i==0) {
					value = sNumber.substring(0, 1);
				}
				else {
					value = sNumber.substring(i,i+1);
				}
				if(!mapdata.containsKey(value)) {
					mapdata.put(value, value);
					uniquenumber = true;
				}
				else {
					uniquenumber = false;
					i=nLen+1;
				}
			}
			nCounter++;
		}
		return number;
	}
	
	public static int[] generateInts(int start, int end, int required) {
		int[] intarray = new int[required];
		int numb = RandomNumberGenerator.generate(start, end);
		Map mapData = new HashMap(required);
		for(int i=0; i<required; i++) {
			while(mapData.containsKey(String.valueOf(numb))) {
				numb = RandomNumberGenerator.generate(start, end);
			}
			mapData.put(String.valueOf(numb), String.valueOf(numb));
			intarray[i] = numb;
		}
		
		return intarray;
	}	
	
	public static int[] generateInts(int start, int end, int required, boolean ascending) {
		int[] intarray = new int[required];
		int numb = RandomNumberGenerator.generate(start, end);
		Map mapData = new HashMap(required);
		for(int i=0; i<required; i++) {
			while(mapData.containsKey(String.valueOf(numb))) {
				numb = RandomNumberGenerator.generate(start, end);
			}
			mapData.put(String.valueOf(numb), String.valueOf(numb));
			intarray[i] = numb;
		}
		
		if(!ascending) {
			return SortingUtil.sort(intarray, ascending);
		}
		
		return intarray;
	}
    
	public static long generateMultiple(int limit, int multiple) {
		int randomnumber = generate(multiple, limit);
		while(randomnumber % multiple != 0) {
			randomnumber = generate(multiple, limit);
		}
		return randomnumber;
	}
	
    public static void main(String[] args) {
//        RandomNumberGenerator rng = new RandomNumberGenerator();
//        System.out.println(rng.generateDouble(5, 3));
//        System.out.println(rng.generateDouble(2, 0));
//        System.out.println(rng.generateDouble(3, 1));
//        System.out.println(rng.generateDouble(0, 4));
//        System.out.println(rng.generateDouble(-5, 4));
    	/*String[] sarray = generate(1, 10, 4);
    	for(int i=0; i<sarray.length; i++) {
    		System.out.println(sarray[i]);
    	}*/
    	System.out.println(generateDouble(1, 0));
    	generateInt(1, 99999);
    	System.out.println("....Multiple of 5: " + generateMultiple(60, 5));
    	System.out.println("....Multiple of 10: " + generateMultiple(60, 10));
    	System.out.println("....Multiple of 15: " + generateMultiple(60, 15));
    	System.out.println("....Multiple of 20: " + generateMultiple(60, 20));
    	System.out.println("....Multiple of 30: " + generateMultiple(60, 30));
    }    
}
